var assert  = require('assert'),
    utility = require('system/utility'),
	Pool    = require('collections/Pool');

PoolTests = (function () {
	var PoolTests = function() {
		var PoolStatistics = function() {
			this._createCount  = 0;
			this._destroyCount = 0;
			this._acquireCount = 0;
		}

		Object.defineProperty(PoolStatistics.prototype, 'createCount', {
			get: function() {
				return this._createCount;
			},
			set: function(value) {
				this._createCount = value;
			}
		});

		Object.defineProperty(PoolStatistics.prototype, 'destroyCount', {
			get: function() {
				return this._destroyCount;
			},
			set: function(value) {
				this._destroyCount = value;
			}
		});

		Object.defineProperty(PoolStatistics.prototype, 'acquireCount', {
			get: function() {
				return this._acquireCount;
			},
			set: function(value) {
				this._acquireCount = value;
			}
		});

		var runTest = function(settings, poolItems, acquireCount, onAcquire, onCreate, onDestroy) {
			var stats = new PoolStatistics;

			var pool = new Pool(
				utility.extend(
					{
						maxPoolSize: poolItems.length,
						create: function(callback) {
							var item = poolItems[stats.createCount];
					
							if (onCreate) {
								onCreate(pool, undefined, item, stats.createCount);
							}
					
							++stats.createCount;
							callback(undefined, item);
						},
						destroy: function(value) {
							stats.destroyCount++;
					
							if (onDestroy) {
								onDestroy(pool, value, stats);
							}
						}
					},
					settings
				)
			);
	
			for (var x = 0; x < acquireCount; ++x) {
				pool.acquire(function(err, value) {
					++stats.acquireCount;
			
					if (onAcquire) {
						onAcquire(pool, err, value, stats);
					}
				});
			}
		}

		var runStandardTest = function(testSettings, done) {
			var x = 0;
	
			utility.do(
				function() {							
					var worker   = this;
					var values   = testSettings[x].data;
					var parallel = this.parallel();
					var settings =
						utility.extend(
							{ minPoolSize: 3, cleanupInterval: 1, expirationTimeout: 4 },
							testSettings[x].settings
						);
			
					runTest(
						settings,
						values,
						values.length * (x + 1),
						function(pool, err, value, stats) {
							pool.release(value);
					
							if (stats.acquireCount <= values.length) {
								assert.strictEqual(value, values[stats.acquireCount - 1]);
							}
					
							// Nothing should be destroyed yet
							assert.strictEqual(stats.destroyCount, 0);
							assert.ok(values.indexOf(value) > -1, 'value must be in the original list');
					
							if (stats.acquireCount === (values.length * (x + 1))) {						
								// Give it a little time to see if anything else gets acquired, released or destroyed.
								setTimeout(function() {
									assert.strictEqual(stats.acquireCount, (values.length * (x + 1)));
									assert.strictEqual(stats.destroyCount, values.length - settings.minPoolSize);
									assert.strictEqual(stats.createCount, values.length);
									++x;
							
									if (x < testSettings.length) {
										worker.repeat();
									}
							
									parallel();
								}, settings.expirationTimeout + 4);
							}
						}
					);
				},
				done
			);
		}

		return {
			'#(settings)' : {
				'minPoolSize should only allow positive integers' : function() {
					assert.throws(function() { new Pool( { minPoolSize : -1  } ); }, RangeError);
					assert.throws(function() { new Pool( { minPoolSize : -10 } ); }, RangeError);
					assert.throws(function() { new Pool( { minPoolSize : '1' } ); }, TypeError);
					assert.throws(function() { new Pool( { minPoolSize : null } ); }, TypeError);
					assert.doesNotThrow(function() { new Pool( { minPoolSize : undefined } ); }, TypeError);
					assert.throws(function() { new Pool( { minPoolSize : {} } ); }, TypeError);
					assert.throws(function() { new Pool( { minPoolSize : [] } ); }, TypeError);
					assert.throws(function() { new Pool( { minPoolSize : function() { } } ); }, TypeError);
				},
				'maxPoolSize should only allow positive integers' : function() {
					assert.throws(function() { new Pool( { maxPoolSize : 0  } ); }, RangeError);
					assert.throws(function() { new Pool( { maxPoolSize : -1  } ); }, RangeError);
					assert.throws(function() { new Pool( { maxPoolSize : -10 } ); }, RangeError);
					assert.throws(function() { new Pool( { maxPoolSize : '1' } ); }, TypeError);
					assert.throws(function() { new Pool( { maxPoolSize : null } ); }, TypeError);
					assert.doesNotThrow(function() { new Pool( { maxPoolSize : undefined } ); }, TypeError);
					assert.throws(function() { new Pool( { maxPoolSize : {} } ); }, TypeError);
					assert.throws(function() { new Pool( { maxPoolSize : [] } ); }, TypeError);
					assert.throws(function() { new Pool( { maxPoolSize : function() { } } ); }, TypeError);
				},
				'expirationTimeout should only allow positive integers' : function() {
					assert.throws(function() { new Pool( { expirationTimeout : 0  } ); }, RangeError);
					assert.throws(function() { new Pool( { expirationTimeout : -1  } ); }, RangeError);
					assert.throws(function() { new Pool( { expirationTimeout : -10 } ); }, RangeError);
					assert.throws(function() { new Pool( { expirationTimeout : '1' } ); }, TypeError);
					assert.throws(function() { new Pool( { expirationTimeout : null } ); }, TypeError);
					assert.doesNotThrow(function() { new Pool( { expirationTimeout : undefined } ); }, TypeError);
					assert.throws(function() { new Pool( { expirationTimeout : {} } ); }, TypeError);
					assert.throws(function() { new Pool( { expirationTimeout : [] } ); }, TypeError);
					assert.throws(function() { new Pool( { expirationTimeout : function() { } } ); }, TypeError);
				},
				'cleanupInterval should only allow positive integers' : function() {
					assert.throws(function() { new Pool( { cleanupInterval : 0  } ); }, RangeError);
					assert.throws(function() { new Pool( { cleanupInterval : -1  } ); }, RangeError);
					assert.throws(function() { new Pool( { cleanupInterval : -10 } ); }, RangeError);
					assert.throws(function() { new Pool( { cleanupInterval : '1' } ); }, TypeError);
					assert.throws(function() { new Pool( { cleanupInterval : null } ); }, TypeError);
					assert.doesNotThrow(function() { new Pool( { cleanupInterval : undefined } ); }, TypeError);
					assert.throws(function() { new Pool( { cleanupInterval : {} } ); }, TypeError);
					assert.throws(function() { new Pool( { cleanupInterval : [] } ); }, TypeError);
					assert.throws(function() { new Pool( { cleanupInterval : function() { } } ); }, TypeError);
				},
				'a null create function should not throw an exception' : function(done) {
					var pool = new Pool( { create : null } );
					
					pool.acquire(function(err, value) {
						done();
					});
				},
				'an undefined create function should not throw an exception' : function(done) {
					var pool = new Pool( { create : null } );
					
					pool.acquire(function(err, value) {
						done();
					});
				},
				'a null destroy function should not throw an exception' : function(done) {
					var pool = new Pool( { cleanupInterval : 1, expirationTimeout: 10, destroy: null });
					
					pool.acquire(function(err, value) {
						pool.release(value);
						
						setTimeout(function() {
							done();
						}, 20);
					});
				},
				'an undefined destroy function should not throw an exception' : function(done) {
					var pool = new Pool( { cleanupInterval : 1, expirationTimeout: 10, destroy: undefined });
					
					pool.acquire(function(err, value) {
						pool.release(value);
						
						setTimeout(function() {
							done();
						}, 20);
					});
				}
			},
			'#acquire(callback)' : {
				'acquire should be asynchronous' : function(done) {
					var pool = new Pool({
						create: function(callback) {
							callback(undefined, '123abc');
						}
					});
					
					var a = 0;
					
					pool.acquire(function(err, value) {
						assert.strictEqual(a, 1);
						assert.strictEqual(value, '123abc');
						done();
					});
					
					++a;
				},
				'maxPoolSize should not be exceeded' : function(done) {
					var a    = 0;
					var b    = 0;
					var pool = new Pool({
						maxPoolSize: 4,
						create: function(callback) {
							callback(undefined, ++a);
						}
					});
					
					for (var x = 0; x < 10; ++x) {
						pool.acquire(function(err, value) {
							++b;
							
							assert.ok(value < 5, 'value should not exceed 4 if minPoolSize is not violated');
							pool.release(value);
							
							if (b === 10) {
								done();
							}
						});
					}
				},
				'acquire should be undefined is a create function was not set' : function(done) {
					var pool = new Pool();
					
					pool.acquire(function(err, value) {
						assert.strictEqual(value, undefined);
						done();
					});
				},
				'an error occuring in the create should not increase the pool size' : function(done) {
					var a    = 0;
					var b    = 0;
					var pool = new Pool({
						maxPoolSize: 5,
						create: function(callback) {
							++a;
							
							if (a === 3) {
								callback(new Error('test error'), undefined);
							} else {
								callback(undefined, a);
							}
						}
					});
					
					for (var x = 0; x < 6; ++x) {
						pool.acquire(function(err, value) {
							++b;
							
							if (b === 3) {
								assert.strictEqual(value, undefined);
								assert.ok(utility.isError(err));
							} else {
								assert.strictEqual(err, undefined);
							}
							
							if (b === 6) {
								done();
							}
						});
					}
				}
			},
			'multiple functions' : {
				'you should be able to acquire and release simple types' : function(done) {
					runStandardTest(
						[
							{ data : [1,2,3,4,5] },
							{ data : ['a','b','c','d','e'] }
						],
						done
					);
				},
				'you should be able to pool complex types' : function(done) {
					var values = [{ a: 123, b: { c :'asdf' } }, null, undefined, ['asdf', 234],5];
					
					runStandardTest(
						[
							{ data : [{ a: 1 }, { b: { c:'a' } }, { d: function() {} }] },
							{ data : [undefined, undefined, null, null, undefined, null] },
							{ data : [function() {}, function(a) {}, function(a,b,c) {}] },
							{ data : [['a','b','c'], [], [1,2,3], ['a','1','b']] },
						],
						done
					);
				},
				'you should be able to pool mixed types' : function(done) {
					runStandardTest(
						[
							{ data : [{ a: 123, b: { c :'asdf' } }, null, undefined, ['asdf', 234], 0] }
						],
						done
					);
					
				},
				'maxPoolSize should be respected' : function(done) {
					runStandardTest(
						[
							{ data : [1,2,3,4] },
							{ data : [5,4,3,2,1] },
							{ data : ['a','b','c','d','e','f','g','h','i','j'] }
						],
						done
					);
				},
				'minPoolSize should be respected' : function(done) {
					runStandardTest(
						[
							{ settings: { minPoolSize: 0 }, data : [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15] },
							{ settings: { minPoolSize: 7 }, data : [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15] },
							{ settings: { minPoolSize: 15 }, data : [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15] }
						],
						done
					);
				},
				'expirationTimeout should be respected' : function(done) {
					var x = 1;
					
					utility.do(
						function() {
							var worker   = this;
							var parallel = this.parallel();
							
							runTest(
								{ 
									minPoolSize: 0,
									cleanupInterval: 1, 
									expirationTimeout: (x * 4) + 20
								},
								[3],
								1,
								function(pool, err, value, stats) {
									pool.release(value);
									assert.strictEqual(stats.destroyCount, 0);
									
									// set a timer to check for a premature destroy
									setTimeout(function() {
										assert.strictEqual(stats.destroyCount, 0);
									}, (x * 4) + 5);
									
									setTimeout(function() {
										++x;
										
										assert.strictEqual(stats.destroyCount, 1);
										
										if (x < 4) {
											worker.repeat();
										}
										
										parallel();
									}, (x * 4) + 25);
								}
							);
						},
						done
					);
				},
				'cleanupInterval should be respected' : function(done) {
					runTest(
						{ 
							minPoolSize: 0,
							cleanupInterval: 15,
							expirationTimeout:5
						},
						[3],
						1,
						function(pool, err, value, stats) {
							pool.release(value);
							assert.strictEqual(stats.destroyCount, 0);
							
							setTimeout(function() {
								assert.strictEqual(stats.destroyCount, 0);
							}, 10);
							
							setTimeout(function() {
								assert.strictEqual(stats.destroyCount, 1);
								done();
							}, 20);
						}
					);
				}
			}
		};
	}

	return PoolTests;
})();

// Hook into commonJS module systems
if (typeof module !== 'undefined' && "exports" in module) {
  module.exports = PoolTests;
}